﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using YAPL.Language.Ast.Utils;
using YAPL.Language.Core;
using YAPL.Language.Core.Types;

namespace YAPL.Language.Ast.Transformation {
	public class FunctionPrecomputationTransform : AstTransformBase, IAstTransform {
		public override Expression Transform(Expression source, out bool treeModified) {
			treeModified = false;

			var root = source;
			var walker = new DefaultAstWalker(root, AstWalkType.PostOrder);
			Expression newExpr = root;
			while (walker.MoveNext()) {

				newExpr = walker.Current;
				if (walker.Current is FunctionCallExpression) {
					var func = walker.Current as FunctionCallExpression;

					//this check is necessary, because library functions can be redefined
					if (!Redefined(source, func.MethodName)) newExpr = PrecomputeFunctionCallExpression(func);
				}

				if (!ReferenceEquals(newExpr, walker.Current)) {
					treeModified = true;
					walker.RewriteCurrent(newExpr);
				}
			}

			return newExpr;
		}

		/// <summary>
		/// precomputes function, if function is in library and not redefined
		/// </summary>
		/// <param name="func">target expression</param>
		/// <returns></returns>
		private static Expression PrecomputeFunctionCallExpression(FunctionCallExpression func) {

			//check wether arguments are constants
			var scope = new Scope();
			for (var i = 0; i<func.Arguments.Count; i++) {
				var arg = func.Arguments[i];
				if (arg is ConstantExpression) {
					var constantArg = (ConstantExpression)arg;
					var yaplValue = YAPLObject.CreateFromObject(constantArg.Type, constantArg.Value);
					scope[YAPLFunction.UNNAMED_PARAMETER + i] = yaplValue;
				} else return func;
			}

			

			var globalFunctions = GCL.Get(ClassNames.GlobalScope).Methods;
			if (globalFunctions.Defined(func.MethodName)) {
				var method = globalFunctions[func.MethodName];
				if (method.CanPrecompute) {
					var result = method.Call(scope);
					return Expressions.Constant(result.GetType(), result.Value);
				}
			}


			return func;
		}

		private static bool Redefined(Expression source, string functionName) {
			return false;
		}
	}
}
